package config

import (
	"compress/gzip"
	"io"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"gitee.com/bjf-fhe/apicat/errors"
	"gitee.com/bjf-fhe/apicat/openapi"
	"gitee.com/bjf-fhe/apicat/utils"
	"github.com/getkin/kin-openapi/openapi3"
	"github.com/sirupsen/logrus"
)

const notifyForNoAPI = `----------------------------
您提供的OpenAPI定义文件加载错误，本次将使用常见API定义
作为定义文件，请编辑OpenAPI定义文件，以便得到更准确的
接口分析功能，包括未定义API访问判断等错误判断都需要使
用OpenAPI定义才可获得。

如何编辑OpenAPI文件请参考：

https://gitee.com/bjf-fhe/apicat/blob/master/edit-openapi.md

----------------------------
`

type Config struct {
	Definition string
	UserName   string
	Password   string
	Config     string
	// Format          string
	//除了指定Server外，允许单独指定Url前缀
	BaseUrl         string
	Server          string
	ServerVariables []string
	// 日志来源模式，支持nginx/aliyun/aws
	SourceMode      string
	StaticFileTypes []string
	Debug           int
	TimeFormat      string
	File            string
	//用于加载常见URL
	WellknownAPIs string
	wellknown     *openapi3.T

	//缓存文件的路径
	DbName string
	//IP Hash长度
	IPHashLength int

	//是否启用远程汇聚模式，并作为汇聚机 --main
	RemoteAggregationAsMain bool
	//是f启用远程汇聚模式,并作为宿主机 --sub
	RemoteAggregationAsSub bool
	//远程汇聚模式下，汇聚的目标服务器地址 --main-server
	RemoteAggregationMainServer string

	reader io.Reader
}

func (c *Config) IsSub() bool {
	return c.RemoteAggregationAsSub
}

func (c *Config) IsMain() bool {
	return c.RemoteAggregationAsMain
}

func (c *Config) GetTimeFormat() string {
	return c.TimeFormat
}

func (c *Config) GetFileName() string {
	return c.File
}

func (c *Config) IsLocalFile() bool {
	if c.reader != nil {
		//reader模式，统一认为是本地文件
		return true
	}
	_, err := os.Stat(c.File)
	if err == nil {
		return true
	}
	if !os.IsNotExist(err) {
		logrus.Error("check file exists error:", err)
	}
	return false
}

func (c *Config) Check(loadWellknown bool) (err error) {
	var logLvl = logrus.Level(c.Debug)
	logrus.Info("日志级别开始设置为", logLvl)
	logrus.SetLevel(logLvl)

	var wellknown = "./wellknown.yaml"
	if c.WellknownAPIs != "" {
		wellknown = c.WellknownAPIs
	} else if !loadWellknown {
		return nil
	} else {
		_, err := os.Stat(wellknown)
		if err != nil && os.IsNotExist(err) {
			logrus.Errorln("当前目录没有wellknown.yaml文件，将尝试从用户目录读取")
			var home string
			home, err = os.UserHomeDir()
			if err == nil {
				wellknown = filepath.Join(home, ".apicat", "wellknown.yaml")
			}
		}
	}
	{
		var err error //允许wellknown加载错误，所以该错误独立处理
		var loader = openapi3.NewLoader()
		c.wellknown, err = loader.LoadFromFile(wellknown)
		if err == nil {
			logrus.Errorln("加载Wellknown文件：" + wellknown + "成功")
		} else {
			logrus.Errorln("指定的Wellknown文件路径"+wellknown+"加载错误", err)
			logrus.Error(`wellknown文件用于定义常见的访问路径，以加强URL判断准确性，可以访问以下路径获取：https://gitee.com/bjf-fhe/apicat/raw/master/example/wellknown.yaml`)
			err = nil
		}
	}

	return errors.Wrap(err, errors.ConfigCheckError)
}

func (c *Config) IsStaticPath(path string) bool {
	for _, v := range c.StaticFileTypes {
		if strings.HasSuffix(path, v) {
			return true
		}
	}
	return false
}

func (c *Config) IsWellknownView(path, method string) bool {
	if c.wellknown != nil {
		pathDef, ok := c.wellknown.Paths[path]
		if ok {
			op := openapi.GetOperation(pathDef, method)
			return op != nil

		}
	}
	return false
}

//LoadWellknownAsApi 将wellknown作为定义文件进行加载，主要为了初始使用或者体验使用，本函数调用时，wellknown应该已经加载完毕
func (c *Config) LoadWellknownAsApi() (api *openapi3.T, baseUrl string, err error) {
	if c.wellknown == nil {
		return nil, "", errors.New(errors.DefinitionWellknownEmpty)
	}
	baseUrl, err = c.getBaseUrlFromDef(c.wellknown)
	return c.wellknown, baseUrl, err
}

func (c *Config) getBaseUrlFromDef(api *openapi3.T) (baseUrl string, err error) {
	var iS int

	//优先使用BaseUrl，其次使用Server
	if c.BaseUrl != "" {
		baseUrl = c.BaseUrl
	} else if c.Server != "" {
		iS, err = strconv.Atoi(c.Server)
	}

	if err == nil {
		//根据server获得baseUrl配置
		if len(api.Servers) == 0 {
			logrus.Errorln("OpenAPI定义文档并未提供服务器信息，使用默认前缀'/'")
		} else if len(api.Servers) > iS+1 {
			logrus.Errorf("提供的服务器信息编号%d不存在，提供的服务器信息包括：", iS)
			for k, v := range api.Servers {
				logrus.Errorf("[%d](%s)%s", k, v.Description, v.URL)
			}
		} else {
			var inputedVariables = make(map[string]string)
			for _, arg := range c.ServerVariables {
				parts := strings.SplitN(arg, ":", 2)
				if len(parts) == 2 {
					inputedVariables[parts[0]] = parts[1]
				}
			}
			var server = api.Servers[iS]

			for k := range server.Variables {
				if inputed, ok := inputedVariables[k]; ok {
					server.Variables[k].Default = inputed
				}
			}
			baseUrl, err = server.BasePath()
			if err != nil {
				err = errors.Wrap(err, errors.DefinitionServerError)
			}
		}
	} else {
		err = errors.Wrap(err, errors.ConfigCheckServerConfigWrong)
	}
	return
}

func (c *Config) GetBaseUrlFromDef(api *openapi3.T) (baseUrl string, err error) {

	baseUrl, err = c.getBaseUrlFromDef(api)
	if err != nil {
		return
	}

	var u *url.URL

	u, err = url.Parse(baseUrl)

	if err != nil {
		err = errors.Wrap(err, errors.DefinitionServerParseError)
		return
	}

	baseUrl = u.Path //因为nginx日志没有域名信息，只需要path部分

	if baseUrl != "/" {
		logrus.Infoln("判定的根路径为：", baseUrl)
	} else {
		baseUrl = ""
	}

	logrus.Info("开始进行日志读取分析")
	return
}

// LoadApi 加载API定义文件, 如果loadWellknown为true，则在加载失败时，将尝试从wellknown中加载
func (c *Config) LoadApi(loadWellknown bool) (api *openapi3.T, baseUrl string, err error) {
	err = c.Check(loadWellknown)
	if err == nil {
		api, err = openapi.Load(c.Definition, c.UserName, c.Password)
		if err == nil {
			baseUrl, err = c.GetBaseUrlFromDef(api)
		} else if c.RemoteAggregationAsSub {
			//如果是远程聚合，不需要加载定义文件，所以不需要报错
			return nil, "", nil
		} else {
			//使用wellknown作为定义
			logrus.Errorln("API加载错误", err)
			os.Stderr.Write([]byte(notifyForNoAPI))
			api, baseUrl, err = c.LoadWellknownAsApi()
		}
	}
	return
}

func (c *Config) OpenFile() (io.Reader, error) {

	var readCloser io.ReadCloser
	var err error

	if c.reader != nil {
		if rc, ok := c.reader.(io.ReadCloser); ok {
			readCloser = rc
		}
	} else {
		readCloser, err = os.Open(c.File)
		if err != nil {
			return nil, err
		}
	}

	if strings.HasSuffix(c.File, ".gz") {
		var zc = utils.ZipReadCloser{}
		if readCloser != nil {
			zc.Zip, err = gzip.NewReader(readCloser)
			zc.Low = readCloser
		} else {
			//readCloser为nil只可能c.reader不为空且c.reader不是readcloser
			zc.Zip, err = gzip.NewReader(c.reader)
		}
		return &zc, err
	} else if readCloser != nil {
		return readCloser, nil
	}
	return c.reader, nil
}

func (c *Config) HasFeatureDB() bool {
	return true
}
